/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
 
package com.cburch.logisim.std.io;

import java.awt.Color;
import java.awt.Graphics;

import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeOption;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Attributes;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceData;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.UItools.GraphicsUtil;
import com.cburch.logisim.util.IntegerFactory;
import com.cburch.logisim.util.Strings;

// TODO repropagate when rows/cols change

public class DotMatrix extends InstanceFactory {
    static final AttributeOption INPUT_SELECT
        = new AttributeOption("select", Strings.getter("ioInputSelect", Strings.stdSource));
    static final AttributeOption INPUT_COLUMN
        = new AttributeOption("column", Strings.getter("ioInputColumn", Strings.stdSource));
    static final AttributeOption INPUT_ROW
        = new AttributeOption("row", Strings.getter("ioInputRow", Strings.stdSource));

    static final AttributeOption SHAPE_CIRCLE
        = new AttributeOption("circle", Strings.getter("ioShapeCircle", Strings.stdSource));
    static final AttributeOption SHAPE_SQUARE
        = new AttributeOption("square", Strings.getter("ioShapeSquare", Strings.stdSource));
    
    static final Attribute ATTR_INPUT_TYPE
        = Attributes.forOption("inputtype",
            Strings.getter("ioMatrixInput", Strings.stdSource),
            new Object[] { INPUT_COLUMN, INPUT_ROW, INPUT_SELECT });
    static final Attribute ATTR_MATRIX_COLS
        = Attributes.forIntegerRange("matrixcols",
                Strings.getter("ioMatrixCols", Strings.stdSource), 1, Value.MAX_WIDTH);
    static final Attribute ATTR_MATRIX_ROWS
        = Attributes.forIntegerRange("matrixrows",
                Strings.getter("ioMatrixRows", Strings.stdSource), 1, Value.MAX_WIDTH);
    static final Attribute ATTR_ON_COLOR = Attributes.forColor("color",
            Strings.getter("ioOnColor", Strings.stdSource));
    static final Attribute ATTR_OFF_COLOR = Attributes.forColor("offcolor",
            Strings.getter("ioOffColor", Strings.stdSource));
    static final Attribute ATTR_DOT_SHAPE = Attributes.forOption("dotshape",
            Strings.getter("ioMatrixShape", Strings.stdSource),
            new Object[] { SHAPE_CIRCLE, SHAPE_SQUARE });

    public DotMatrix() {
        super("DotMatrix", Strings.getter("dotMatrixComponent", Strings.stdSource));
        setAttributes(new Attribute[] {
                ATTR_INPUT_TYPE, ATTR_MATRIX_COLS, ATTR_MATRIX_ROWS,
                ATTR_ON_COLOR, ATTR_OFF_COLOR, ATTR_DOT_SHAPE
            }, new Object[] {
                INPUT_COLUMN, IntegerFactory.create(5), IntegerFactory.create(7),
                Color.GREEN, Color.DARK_GRAY, SHAPE_SQUARE
            });
        setIconName("dotmat.gif");
    }

    public Bounds getOffsetBounds(AttributeSet attrs) {
        Object input = attrs.getValue(ATTR_INPUT_TYPE);
        int cols = ((Integer) attrs.getValue(ATTR_MATRIX_COLS)).intValue();
        int rows = ((Integer) attrs.getValue(ATTR_MATRIX_ROWS)).intValue();
        if(input == INPUT_COLUMN) {
            return Bounds.create(-5, -10 * rows, 10 * cols, 10 * rows);
        } else if(input == INPUT_ROW) {
            return Bounds.create(0, -5, 10 * cols, 10 * rows);
        } else { // input == INPUT_SELECT
            if(rows == 1) {
                return Bounds.create(0, -5, 10 * cols, 10 * rows);
            } else {
                return Bounds.create(0, -5 * rows + 5, 10 * cols, 10 * rows);
            }
        }
    }

    protected void configureNewInstance(Instance instance) {
        instance.setAttributeReadOnly(ATTR_INPUT_TYPE, true);
        instance.addAttributeListener();
        updatePorts(instance);
    }

    protected void instanceAttributeChanged(Instance instance, Attribute attr) {
        if(attr == ATTR_MATRIX_ROWS || attr == ATTR_MATRIX_COLS) {
            instance.recomputeBounds();
            updatePorts(instance);
        }
    }
    
    private void updatePorts(Instance instance) {
        Object input = instance.getAttributeValue(ATTR_INPUT_TYPE);
        int rows = ((Integer) instance.getAttributeValue(ATTR_MATRIX_ROWS)).intValue();
        int cols = ((Integer) instance.getAttributeValue(ATTR_MATRIX_COLS)).intValue();
        Port[] ps;
        if(input == INPUT_COLUMN) {
            ps = new Port[cols];
            for(int i = 0; i < cols; i++) {
                ps[i] = new Port(10 * i, 0, Port.INPUT, rows);
            }
        } else if(input == INPUT_ROW) {
            ps = new Port[rows];
            for(int i = 0; i < rows; i++) {
                ps[i] = new Port(0, 10 * i, Port.INPUT, cols);
            }
        } else {
            if(rows <= 1) {
                ps = new Port[] { new Port(0, 0, Port.INPUT, cols) };
            } else if(cols <= 1) {
                ps = new Port[] { new Port(0, 0, Port.INPUT, rows) };
            } else {
                ps = new Port[] {
                        new Port(0, 0, Port.INPUT, cols),
                        new Port(0, 10, Port.INPUT, rows)
                };
            }
        }
        instance.setPorts(ps);
    }

    public void propagate(InstanceState state) {
        Object input = state.getAttributeValue(ATTR_INPUT_TYPE);
        int valsLength = state.getInstance().getPorts().size();
        State data = (State) state.getData();
        if(data == null) {
            data = new State(valsLength);
            state.setData(data);
        }
        int[] vals = data.vals;
        if(vals.length != valsLength) {
            vals = new int[valsLength];
            data.vals = vals;
        }
        boolean allDefined = true;
        for(int i = 0; i < vals.length; i++) {
            Value val = state.getPort(i);
            if(!val.isFullyDefined()) allDefined = false;
            vals[i] = val.toIntValue();
        }
        if(input == null || input == INPUT_SELECT) {
            if(!allDefined) {
                vals[0] = -1;
                if(vals.length >= 2) vals[1] = -1;
            }
        }
    }

    public void paintInstance(InstancePainter painter) {
        Object input = painter.getAttributeValue(ATTR_INPUT_TYPE);
        Color onColor = (Color) painter.getAttributeValue(ATTR_ON_COLOR);
        Color offColor = (Color) painter.getAttributeValue(ATTR_OFF_COLOR);
        boolean drawSquare = painter.getAttributeValue(ATTR_DOT_SHAPE) == SHAPE_SQUARE;
        int rows = ((Integer) painter.getAttributeValue(ATTR_MATRIX_ROWS)).intValue();
        int cols = ((Integer) painter.getAttributeValue(ATTR_MATRIX_COLS)).intValue();

        State data = (State) painter.getData();
        int[] state = data == null ? new int[0] : data.vals;
        
        Bounds bds = painter.getBounds();
        boolean showState = painter.getShowState();
        Graphics g = painter.getGraphics();
        for(int j = 0; j < rows; j++) {
            for(int i = 0; i < cols; i++) {
                int x = bds.getX() + 10 * i;
                int y = bds.getY() + 10 * j;
                if(showState) {
                    boolean value;
                    if(input == INPUT_COLUMN) {
                        int k = i < state.length ? state[i] : 0;
                        value = (k >> (rows - 1 - j) & 1) != 0;
                    } else if(input == INPUT_ROW) {
                        int k = j < state.length ? state[j] : 0;
                        value = (k >> (cols - 1 - i) & 1) != 0;
                    } else if(rows <= 1) { // input == INPUT_SELECT
                        int k = 0 < state.length ? state[0] : 0;
                        value = ((k >> (cols - 1 - i)) & 1) != 0;
                    } else if(cols <= 1) {
                        int k = 0 < state.length ? state[0] : 0;
                        value = ((k >> (rows - 1 - j)) & 1) != 0;
                    } else {
                        int k0 = 0 < state.length ? state[0] : 0;
                        int k1 = 1 < state.length ? state[1] : 0;
                        value = ((k0 >> (cols - 1 - i))
                                & (k1 >> (rows - 1 - j)) & 1) != 0;
                    }
                    g.setColor(value ? onColor : offColor);
                    
                    if(drawSquare) g.fillRect(x, y, 10, 10);
                    else g.fillOval(x + 1, y + 1, 8, 8);
                } else {
                    g.setColor(Color.GRAY);
                    g.fillOval(x + 1, y + 1, 8, 8);
                }
            }
        }
        g.setColor(Color.BLACK);
        GraphicsUtil.switchToWidth(g, 2);
        g.drawRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
        GraphicsUtil.switchToWidth(g, 1);
        painter.drawPorts();
    }
    
    private static class State implements InstanceData, Cloneable {
        private int[] vals;
        
        public State(int length) {
            vals = new int[length];
        }
        
        public Object clone() {
            try {
                State ret = (State) super.clone();
                ret.vals = (int[]) this.vals.clone();
                return ret;
            } catch(CloneNotSupportedException e) {
                return null;
            }
        }
    }
}
